package system.data;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.ListIterator;
import system.Convert;

/***
 * DataTable 是 DataRow 的列表集合，并且其中包含了一个列名和列索引的哈希表
 * @author Fred
 */
public class DataTable extends ArrayList<DataRow>
{
    public HashMap<Object,Integer> column_index_map;
    public int column_count;

    public DataTable()
    {
        this.column_index_map = new HashMap<Object,Integer>();
        this.column_count = 0;
    }

    /***
     * 将一列的所有记录转化为一个数组
     * @param field 投影的列名
     * @return
     */
    public Object[] collect(String field)
    {
        if(this.size()==0)
            return null;

        HashMap map = new HashMap();
        ListIterator<DataRow> iter = this.listIterator();

        while(iter.hasNext())
            map.put(iter.next().get(field), null);
        
        return map.keySet().toArray();
    }
 
    /***
     * 将当前表的每一行与FieldHashDataRow用foreign_key关联
     *  fDataRow 必须是以foreign_key为键的Hashtable
     *  用这个方法有一个限制是两个表中同名的字段必须具有同样的含义，不同名的字段具有不同的含义。
     * @param fDataRow
     * @param foreign_key
     */
    public void join(FieldHashDataRow fDataRow,String foreign_key)
    {
        if(fDataRow==null)
            return;

        int size = this.size();

        for(int i=0;i<size;i++)
        {
            DataRow dr = this.get(i);
            DataRow pdr = fDataRow.get(dr.get(foreign_key));
            dr.join(pdr);
        }
    }

    public void addColumn(String name)
    {
        this.column_index_map.put(name, this.column_count++);
    }

    public DataRow newRow()
    {
        return new DataRow(this);
    }

    /***
         * 实现简单的内存 SQL 表达式
         * @param expression
         * @return
         */
    public ArrayList<DataRow> select(String expression)
    {
        Expression e = Expression.FindExpression(expression);
        ArrayList<DataRow> list = new ArrayList<DataRow>();

        for(DataRow row : this)
          if(e.eval(row))
              list.add(row);
        return list;
    }
}

//<editor-fold defaultstate="collapsed" desc="Expression">
class Expression
{
	private String expression;
	private int exp_len;
	private char[] exp_array;
	private ArrayList<String> token_list;
    private boolean is_flushed;
    private char c;
	private StringBuffer sb;
	private int cur_token_index;
	private int token_count;

	public Expression(String expression)
	{
		this.expression = expression;
		this.exp_len = expression.length();
		this.exp_array = expression.toCharArray();
		this.token_list = new ArrayList<String>();
		this.is_flushed = true;
		this.cur_token_index = -1;
		this.sb = new StringBuffer();
		this.initTokenList();
		this.token_count = this.token_list.size();

	}

	private void flush()
	{
		if(this.is_flushed)
			return;

		this.token_list.add(this.sb.toString());
		this.sb = new StringBuffer();
		this.is_flushed = true;
	}

	private void collect()
	{
		this.sb.append(c);
        this.is_flushed = false;
	}

	private boolean nextToken()
	{

		if(this.cur_token_index<this.token_count-1)
		{
			this.cur_token_index++;
			return true;
		}

		return false;
	}

	private String currentToken()
	{
		return this.token_list.get(this.cur_token_index);
	}

    private void initTokenList()
    {
    	for(int i=0;i<this.exp_len;i++)
    	{
    		c = exp_array[i];
    		if(Character.isWhitespace(c))
            {
                this.flush();
                continue;
            }

            switch(c)
            {
            	case '<' :

            		this.flush();
            		if(i+1<this.exp_len && exp_array[i+1]=='=')
                    {
                        this.token_list.add("<=");
                        i++;
                    }
                    else
                    {
                    	this.token_list.add("<");
                    }
            		break;

            	case '>' :

            		this.flush();
            		if(i+1<this.exp_len && exp_array[i+1]=='=')
                    {
                        this.token_list.add(">=");
                        i++;
                    }
                    else
                    {
                    	this.token_list.add(">");
                    }
            		break;

            	case '(' :

            		this.flush();
            		this.token_list.add("(");
            		break;

            	case ')' :

            		this.flush();
            		this.token_list.add(")");
            		break;

            	case '=' :

            		if(i+1<this.exp_len && exp_array[i+1]=='=')
            		{
            			this.flush();
            			this.token_list.add("==");
            			i++;
            		}
            		else
            		{
            			this.collect();
            		}
            		break;

            	case '!' :

            		if(i+1<this.exp_len && exp_array[i+1]=='=')
            		{
            			this.flush();
            			this.token_list.add("!=");
            			i++;
            		}
            		else
            		{
            			this.collect();
            		}
            		break;


            	default:

            		this.collect();
            		break;
            }
    	}

    	this.flush();
    }

    public boolean eval(DataRow dr)
    {
    	this.nextToken();
    	boolean result = this.doAndOr(dr);
        this.cur_token_index = -1;
        return result;
    }

    private boolean doAndOr(DataRow dr)
    {
    	boolean result = this.doNot(dr);
    	String op = "";

        boolean result_right;

    	while((op=this.currentToken()).equals("and") || op.equals("or"))
    	{
    		this.nextToken();

    		if(op.equals("and"))
            {
                result_right = this.doNot(dr);
    			result = result && result_right;
            }
    		else
            {
                result_right = this.doNot(dr);
    			result = result || result_right;
            }
    	}

    	return result;
    }

    private boolean doNot(DataRow dr)
    {
    	String op="";

    	if((op=this.currentToken()).equals("not"))
    		this.nextToken();

    	boolean result = this.doBrackets(dr);

    	if(op.equals("not"))
    			return !result;

    	return result;
    }

    private  boolean doBrackets(DataRow dr)
    {
    	boolean result;
    	if(this.currentToken().equals("("))
    	{
    		this.nextToken();
    		result = this.doAndOr(dr);
    		this.nextToken();
    	}
    	else
    	{
    		result = this.doCompare(dr);
    	}

    	return result;
    }

    private boolean  doCompare(DataRow dr)
    {
    	Object field = dr.get(this.currentToken());
    	this.nextToken();
    	String opt = this.currentToken();
    	this.nextToken();
    	String value = this.currentToken();
        this.nextToken();

    	if(opt.equals("like"))
    	{
    		return isLike(field,value);
    	}
    	else if(opt.equals(">"))
    	{
    		return isGreat(field,value);
    	}
        else if(opt.equals("<"))
    	{
    		return isLess(field,value);
    	}
    	else if(opt.equals("=="))
    	{
    		return isEquals(field,value);
    	}
        else if(opt.equals(">="))
    	{
    		return isGreatEquals(field,value);
    	}
        else if(opt.equals("<="))
    	{
    		return isLessEquals(field,value);
    	}
        else if(opt.equals("!="))
        {
            return isNotEquals(field,value);
        }

    	return false;

    }
    

//<editor-fold  defaultstate="collapsed" desc="tool method">

    private static boolean isLike(Object field,String value)
    {
    	int len = value.length();
		if(value.startsWith("'%") && value.endsWith("%'"))
			return Convert.toString(field).contains(value.substring(2,len-2));
		else if(value.startsWith("'%"))
			return Convert.toString(field).endsWith(value.substring(2,len-1));
		else if(value.endsWith("%'"))
			return Convert.toString(field).startsWith(value.substring(1,len-2));
		else
			return Convert.toString(field).equals(value.substring(1,len-1));
    }

    private static boolean isLess(Object field,String value)
    {
    	if(field instanceof Number)
            return Convert.toFloat(field)<Convert.toFloat(value);
        return Convert.toString(field).compareTo(value.substring(1,value.length()-1))<0;
    }

    private static boolean isGreat(Object field,String value)
    {
        if(field instanceof Number)
            return Convert.toFloat(field)>Convert.toFloat(value);
        return Convert.toString(field).compareTo(value.substring(1,value.length()-1))>0;
    }

    private static boolean isEquals(Object field,String value)
    {
         if(value.equals("null"))
            return field==null;

        if(field instanceof Number)
            return Convert.toFloat(field)==Convert.toFloat(value);
        if(field instanceof Boolean)
            return Convert.toBool(field)==Convert.toBool(value); 

        return Convert.toString(field).equals(value.substring(1,value.length()-1));
    }

    private static boolean isNotEquals(Object field,String value)
    {
        if(value.equals("null"))
            return field!=null;

        if(field instanceof Number)
            return Convert.toFloat(field)!=Convert.toFloat(value);
        if(field instanceof Boolean)
            return Convert.toBool(field)==Convert.toBool(value);

        return !Convert.toString(field).equals(value.substring(1,value.length()-1));
    }

    private static boolean isLessEquals(Object field,String value)
    {
    	if(field instanceof Number)
            return Convert.toFloat(field)<=Convert.toFloat(value);
        return Convert.toString(field).compareTo(value.substring(1,value.length()-1))<=0;
    }

    private static boolean isGreatEquals(Object field,String value)
    {
        if(field instanceof Number)
            return Convert.toFloat(field)>=Convert.toFloat(value);
        return Convert.toString(field).compareTo(value.substring(1,value.length()-1))>=0;
    }
//</editor-fold>

    private static HashMap<String,Expression> expression_map = new HashMap<String,Expression>();

    public static Expression FindExpression(String expression)
    {
        Expression e = expression_map.get(expression);
        if(e==null)
        {
            e = new Expression(expression);
            expression_map.put(expression, e);
        }
        return e;
    }
    
}
 //</editor-fold>